/**
 * 
 */
package astri.pdcontrol.commclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.Socket;

import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import  astri.pdcontrol.commclient.AstCommMessage;
import  astri.pdcontrol.commclient.AstCommMessage.AstCommMessageHeader;
/**
 * Class for Socket-based (TCP) Communication Client developed by ASTRI.
 * @author hspoon@ASTRI
 * @version 2.0
 */
public final class AstCommClient extends Thread{
	static final boolean debug_on = true;
	
	static final int ASTCOMMCLIENT_STATE_INIT       		    =  0x00000000;	/* starting   */
	static final int ASTCOMMCLIENT_STATE_OPENED       		    =  0x00000004;	/* starting   */
	static final int ASTCOMMCLIENT_STATE_DISCOVERY      		=  0x00000008;	/* discovery  */
	static final int ASTCOMMCLIENT_STATE_CONNECTING     		=  0x00000010;	/* connecting,nonblock connecting */
	static final int ASTCOMMCLIENT_STATE_CONNECTED      		=  0x00000020;	/* connected  */
	//static final int ASTCOMMCLIENT_STATE_RECONNECT      		=  0x00000040;	/* re-connect,reconnect the socket after it is disconnected */
	//static final int ASTCOMMCLIENT_STATE_WAITCONNECT    		=  0x00000080;	/* wait connect, only set in ClientThread to skip discovery*/
	static final int ASTCOMMCLIENT_STATE_CLOSING				=  0x00000100;
	static final int ASTCOMMCLIENT_STATE_CLOSED       		    =  0x00000200;	/* stopping   */
	//static final int ASTCOMMCLIENT_STATE_LOGIN           		=  0x00000400;  /* login      */
	//static final int ASTCOMMCLIENT_STATE_RECONNECTREQ    		=  0x00000800;  /* reconnect WHEN connected, differ from ASTCOMMCLIENT_STATE_RECONNECT which reconnect on disconnected */
	//static final int ASTCOMMCLIENT_STATE_RETRYCONNECTPORT		=  0x00001000;  /* retry connecting HMC with different ports */
	//static final int ASTCOMMCLIENT_STATE_CANCELCONNECTING		=  0x00002000;  /* to cancel the pending connection,nonblock */
	
	public AstCommClientConfig 			config 			= null;
	Socket							commSocket 		= null;
	SocketChannel					commSocketChannel = null;
	Selector						commSelector	= null;
	MulticastSocket					discoverySocket = null;
	
	volatile boolean				stoppingThread 	= false;
	
	int								lastState 		= ASTCOMMCLIENT_STATE_INIT;
	volatile int 					state 			= ASTCOMMCLIENT_STATE_INIT;
	
	long							lastDiscoveryTime 	= 0;
	int								discoveryDuration = 0;
	long							discoveryStartTime = 0;
	
	/**
	 * A List to store all discovery SocketAddress after calling startDiscoveryRequest()
	 */
	public List<InetSocketAddress>	discoveryList 		= null;
	
	InetSocketAddress				nonBlockingSocketAddress 	= null;
	int								nonBlockingConnectTimeout 	= 0;
	
	
	/**
	 * Constructor use default AstCommClientConfig
	 */
	public AstCommClient() {
		this(new AstCommClientConfig());
	}
	
	/**
	 * Constructor use default AstCommClientConfig with specified listener
	 * @param listener	Event listener to handle event from AstCommClient
	 */
	public AstCommClient(AstCommClientListener listener) {
		DEBUG("Create AstCommClient obj");
		AstCommClientConfig conf = new AstCommClientConfig();
		conf.listener 		= listener;
		config = conf;
		init();
	}
	
	/**
	 *  Constructor with specified clientTimeout, serverTimeout and listener
	 * @param clientTimeout		Default timeout value for send timeout
	 * @param serverTimeout		Default Timeout from Server (in msec), e.g. read Timeout, connect Timeout
	 * @param listener			Event listener to handle event from AstCommClient
	 */
	public AstCommClient(int clientTimeout, int serverTimeout, AstCommClientListener listener) {     
		DEBUG("Create AstCommClient obj");
		AstCommClientConfig conf = new AstCommClientConfig();
		conf.clientTimeout	= clientTimeout;       
		conf.serverTimeout	= serverTimeout;  
		conf.listener 		= listener;
		
		config = conf;
		init();
		
	}
	
	/**
	 * Constructor with specified AstCommClientConfig
	 * @param conf	Specified AstCommClientConfig
	 */
	public AstCommClient(AstCommClientConfig conf) {
		config = conf;
		init();
	}
	
	/**
	 * [Internal]
	 * Initialize value for AstCommClient 
	 */
	private void init() {
		commSocket = null;
		commSelector = null;
		commSocketChannel = null;
		discoverySocket = null;
		stoppingThread = false;
		state = ASTCOMMCLIENT_STATE_INIT;
		lastDiscoveryTime = 0;
		discoveryDuration = 0;
		discoveryStartTime = 0;
		lastState = state;
		
		discoveryList = new ArrayList<InetSocketAddress>();
		nonBlockingSocketAddress = null;
		nonBlockingConnectTimeout = 0;	
	}
	
	@Override
	protected void finalize() throws Throwable {
		INFO("finalize()!!!!!!!!!!!!!!!!!");
		try {
	    	disconnect();
	        close();        // close open files
	    } finally {
	        super.finalize();
	    }
	}
	
	/**
	 * [Internal]
	 * To change state for AstCommClient and store the "last state"
	 * @param new_state	state to be set
	 */
	private void changeState(int new_state) {
		lastState = state;
		state = new_state;
	}
	
	/**
	 * To start the worker thread for AstCommClient
	 */
	public void open()	{
		if(this.isAlive())
		{
			INFO("Thread already started");
		}
		else {
			stoppingThread = false;
			start();
			INFO("Thread started");
		}
		changeState(ASTCOMMCLIENT_STATE_OPENED);
		
	}
	
	/**
	 * To close the worker thread in next loop
	 */
	public void close() {
		stoppingThread = true;
		changeState(ASTCOMMCLIENT_STATE_CLOSING);
		INFO("Thread is closing");
	}

	/**
	 * To connect server in blocking mode with timeout
	 * @param addr		server IP Address + Port
	 * @param timeout	connect timeout
	 * @return			connect success?
	 */
	public boolean connectTo(InetSocketAddress addr, int timeout) {
		INFO("Try connect to " + addr +" ...");
		try {
			commSelector = Selector.open();
			commSocketChannel = SocketChannel.open();
			
		} catch (IOException e2) {
			DEBUG("fail to create socket Channel");
			e2.printStackTrace();
			return false;
		}
		commSocket = commSocketChannel.socket();
		try {
			commSocket.connect(addr, timeout);
		} catch (IOException e1) {
			DEBUG("fail to connect");
			e1.printStackTrace();
			return false;
		}
		
		DEBUG("connected? " + commSocket.isConnected());
		if(!commSocket.isConnected()) {
			DEBUG("fail to connect");
			return false;
		}
		
		try {
			commSocketChannel.configureBlocking(false);
		} catch (IOException e) {
			DEBUG("fail to set non Blocking");
			e.printStackTrace();
			return false;
		}
		
		try {
			
			commSocket.setSoTimeout(config.serverTimeout);
		} catch (SocketException e1) {
			DEBUG("fail to set read timeout");
			e1.printStackTrace();
			return false;
		}
		
		// Register the channel with selector, listening for all events
		try {
			commSocketChannel.register(commSelector, commSocketChannel.validOps());
		} catch (ClosedChannelException e) {
			DEBUG("fail to register selector");
			e.printStackTrace();
			return false;
		}
		
		changeState(ASTCOMMCLIENT_STATE_CONNECTED);
		INFO("Connected!");
		
		if(config.listener != null) {
			INFO("send onConnect to Upper Level");
			config.listener.onConnect(commSocket);
		}
		return true;
	}
	
	/**
	 * To connect server in blocking mode with timeout
	 * @param ipAddr	server IP Address to be connected
	 * @param port		server Port to be connected
	 * @return			successful connected?
	 */
	public boolean connectTo(String ipAddr, int port) {
		return connectTo(ipAddr, port, config.serverTimeout);
	}
	
	/**
	 * To connect server in blocking mode with timeout
	 * @param ipAddr	server IP Address to be connected
	 * @param port		server Port to be connected
	 * @param timeout	connect Timeout
	 * @return			successful connected?
	 */
	public boolean connectTo(String ipAddr, int port, int timeout) {
		return connectTo(new InetSocketAddress(ipAddr, port), timeout);
	}
	
	/**
	 * To connect server in non-blocking mode
	 * @param ipAddr	server IP Address to be connected
	 * @param port		server Port to be connected
	 */
	public void connectToNonBlock(String ipAddr, int port) {
		connectToNonBlock(ipAddr, port, config.serverTimeout);
	}
	/**
	 * To connect server in non-blocking mode
	 * @param ipAddr	server IP Address to be connected
	 * @param port		server Port to be connected
	 * @param timeout	connect timeout
	 */
	public void connectToNonBlock(String ipAddr, int port, int timeout) {
		nonBlockingSocketAddress = new InetSocketAddress(ipAddr, port);
		nonBlockingConnectTimeout = timeout;
		changeState(ASTCOMMCLIENT_STATE_CONNECTING);
		INFO("do non-blocking connect to " + nonBlockingSocketAddress);
		
		return;
	}
	
	/**
	 * To stop non-blocking connect
	 */
	public void stopNonBlockConnect() {
		nonBlockingSocketAddress = null;
		nonBlockingConnectTimeout = 0;
		changeState(lastState);
		INFO("stop non-blocking connect");
		
		return;
	}
	
	/**
	 * To disconnect the communication
	 * @return			disconnect success?
	 */
	public boolean disconnect() {
		if (commSocket == null || !commSocket.isConnected())
		{
			INFO("no connected port");
			return true;
		}
		try {
			commSocket.close();
			commSocketChannel.close();
			commSelector.close();
			
		} catch (IOException e) {
			DEBUG("fail to close socket");
			e.printStackTrace();
			return false;
		}
		changeState(ASTCOMMCLIENT_STATE_OPENED);
		if(config.listener != null) {
			INFO("send onDisconnect to Upper Level");
			config.listener.onDisconnect(commSocket);
		}
		INFO("commSocket disconnected");
		commSocket = null;
		return true;
	}
	
	/**
	 * [Internal]
	 * To generate 12-byte Message Header with AstCommMessageHeader format
	 * @param id		Message ID
	 * @param dataLen	Data Length
	 * @return			12-byte Message Header
	 */
	private byte[] genMessageHeader(int id, int dataLen) {
		byte[] header = new byte[12];
		
		if(config.messageEndian == AstEndian.AST_LITTLEENDIAN) {
			AstEndian.int32ToByteLE(config.messageMagic, header, 0);
			AstEndian.int32ToByteLE(id, header, 4);
			AstEndian.int32ToByteLE(dataLen, header, 8);
		}
		else {
			AstEndian.int32ToByteBE(config.messageMagic, header, 0);
			AstEndian.int32ToByteBE(id, header, 4);
			AstEndian.int32ToByteBE(dataLen, header, 8);
		}
		
		return header;
	}
	
	/**
	 * [Internal]
	 * To get AstCommMessageHeader from first 12-byte in data Array
	 * @param data	Data Array
	 * @return		Message Header in AstCommMessageHeader format
	 */
	private AstCommMessageHeader getMessageHeader(byte[] data) {
		AstCommMessage msg = new AstCommMessage();
		if(config.messageEndian == AstEndian.AST_LITTLEENDIAN)	{
			msg.header.magic = AstEndian.byteLEToInt32(data, 0);
			msg.header.id = AstEndian.byteLEToInt32(data, 4);
			msg.header.length = AstEndian.byteLEToInt32(data, 8);
		}
		else
		{
			msg.header.magic = AstEndian.byteBEToInt32(data, 0);
			msg.header.id = AstEndian.byteBEToInt32(data, 4);
			msg.header.length = AstEndian.byteBEToInt32(data, 8);
		}
		
		DEBUG("header magic : " + String.format("0x%x", msg.header.magic));
		DEBUG("header id : " + String.format("0x%x", msg.header.id));
		DEBUG("header length : " + String.format("%d", msg.header.length));
		return msg.header;
		
	}
	
	/**
	 * To send Message with HeaderID and Data to server
	 * @param id	message header ID
	 * @param data	message data
	 * @throws IOException 
	 */
	public void send(int id, byte[] data) throws IOException {
		send(id, data, config.clientTimeout);
	}
	
	/**
	 * To send Message with HeaderID and Data to server
	 * @param id	message header ID
	 * @param data	message data
	 * @param timeout	send timeout
	 * @throws IOException 
	 */
	public void send(int id, byte[] data, int timeout) throws IOException {
		if(commSocket == null || commSocket.isClosed() 
		|| state != ASTCOMMCLIENT_STATE_CONNECTED)
		{
			INFO("socket is not connected!");
			throw new RuntimeException("socket is not connected!");
		}
		
		if (!commSocketChannel.keyFor(commSelector).isWritable())
		{
			throw new RuntimeException("socket is not writable");
		}
		
		int dataLen = 0;
		if(data != null) {
			dataLen = data.length;
		}
		
		long startTime = getCurrentTime();
		byte[] header = genMessageHeader(id, dataLen);
		
		DEBUG(String.format("send msg header = 0x%x, len = %d", id, dataLen));
		ByteBuffer buf = ByteBuffer.allocateDirect(header.length + dataLen);
		buf.clear();
				
		buf.put(header);
		if(data != null) {
			buf.position(header.length);
			buf.put(data);
		}
		
		buf.flip();

		int numBytesWritten = 0;
		while (buf.position() != buf.limit()) {
			numBytesWritten += commSocketChannel.write(buf);
			DEBUG("sent/total = " + buf.position() + "/" + buf.limit());
			if(timeout > 0 && (getCurrentTime() - startTime >= timeout)) {
				disconnect();
				throw new IOException("Send Timeout, " + timeout + "ms, disconnect socket");
			}
		}
		
		return;
	}
	
	/**
	 * To read Message from server
	 * @throws IOException 
	 */
	public void receive() throws IOException {
		receive(config.serverTimeout);
	}
	
	/**
	 * To read Message from server
	 * @param timeout	Read Timeout in msec
	 * @throws IOException 
	 */
	public void receive(int timeout) throws IOException {
		if(commSocket == null || commSocket.isClosed() 
		|| state != ASTCOMMCLIENT_STATE_CONNECTED)
		{
			INFO("socket is not connected!");
			throw new RuntimeException("socket is not connected!");
		}
		
		
		long startTime = getCurrentTime();
		
		ByteBuffer buf = ByteBuffer.allocateDirect(12);
		buf.clear();
		int numBytesRead = 0;
		do {
			numBytesRead += commSocketChannel.read(buf);
			if(timeout > 0 && (getCurrentTime() - startTime >= timeout))
			{
				throw new IOException("receive header timeout, timeout = " + timeout);
			}
		} while(numBytesRead < 12);
		buf.flip();
	    DEBUG("numBytesRead: " + numBytesRead);
	   
		byte[] header = new byte[12];
		buf.get(header);
		AstCommMessage msg = new AstCommMessage();
		msg.header = getMessageHeader(header);
		if(msg.header.length != 0)
		{
			DEBUG("data length = " + msg.header.length);
			buf = ByteBuffer.allocateDirect(msg.header.length);
			buf.clear();
			numBytesRead = 0;
			do {
				numBytesRead += commSocketChannel.read(buf);
				if(timeout > 0 && (getCurrentTime() - startTime >= timeout))
				{
					throw new IOException("receive data timeout");
				}
			} while(numBytesRead < msg.header.length);
			buf.flip();
			byte[] data = new byte[msg.header.length];
			buf.get(data);
			
			msg.data = data;
		}
		
		if(msg.header.magic != config.messageMagic)
		{
			INFO("Wrong magic!");
		}
		else
		{
			INFO("Get Data from Server, msg id = " + String.format("0x%x", msg.header.id));
			
			if(config.listener != null)
			{
				INFO("send onReceive to Upper Level");
				config.listener.onReceive(commSocket, msg);
			}
		}
	
		return;
	}
	
	/**
	 * To send discovery request
	 * @throws IOException 
	 */
	public void startDiscoveryRequest() throws IOException {
		startDiscoveryRequest(0);
		return;
	}
	
	/**
	 * To send discovery request
	 * @param duration	How long discovery mode should do
	 * @throws IOException 
	 */
	public void startDiscoveryRequest(int duration) throws IOException {
		INFO("start DiscoveryRequest");
		discoverySocket = new MulticastSocket(config.discoveryPort);
		discoverySocket.joinGroup(InetAddress.getByName(config.discoveryIpAddr));
		discoverySocket.setSoTimeout(config.serverTimeout);
		discoveryDuration = duration;
		discoveryStartTime = getCurrentTime();
		changeState(ASTCOMMCLIENT_STATE_DISCOVERY);
		return;
	}
	
	/**
	 * To stop discovery request
	 */
	public void stopDiscoveryRequest() {
		if(discoverySocket != null && state == ASTCOMMCLIENT_STATE_DISCOVERY) {
			INFO("stop DiscoveryRequest");
			discoverySocket.close();
			discoverySocket = null;
			discoveryDuration = 0;
			changeState(lastState);
		}
		printDiscoverySocketList();
		return;
	}
	
	/**
	 * To print out current DiscoverySocketList <br />
	 * i.e. printout "this.discoveryList"
	 */
	public void printDiscoverySocketList() {
		InetSocketAddress sock = null;
		System.out.println("\n===printDiscoverySocketList===");
		System.out.println("[index]\tIP Addr:PORT");
		for(int i = 0; i < discoveryList.size(); i++) {
			sock = discoveryList.get(i);
			System.out.printf("[%d]\t%s\n", i, sock.toString());
		}
		System.out.println("===end printDiscoverySocketList===\n");
	}
	
	/**
	 * Connect to index-th Socket in Discovery List with timeout
	 * @param index		index-th Socket to be connected
	 * @return			connect success?
	 */
	public boolean connectToDiscoverySock(int index) {
		return connectToDiscoverySock(index, config.serverTimeout);
	}
	
	/**
	 * Connect to index-th Socket in Discovery List with timeout
	 * @param index		index-th Socket to be connected
	 * @param timeout	connection timeout
	 * @return			connect success?
	 */
	public boolean connectToDiscoverySock(int index, int timeout) {
		INFO("connect to socket index = " + index + " in discovery list");
		if(discoveryList == null || index >= discoveryList.size())
		{
			INFO("No discovered list OR index greater than size");
			return false;
		}
		InetSocketAddress sock = discoveryList.get(index);
		return connectTo(sock.getAddress().getHostAddress(), sock.getPort(), timeout);
	}
	
	/**
	 * [Internal]
	 * To send MultiCasting discovery Message with Message id = defines.ASTCOMM_DISCOVER
	 */
	private void sendDiscoveryMessage() {
		DEBUG("send discovery message");
		lastDiscoveryTime = getCurrentTime();
		
		byte[] header = new byte[12];
		header = genMessageHeader(defines.ASTCOMM_DISCOVER, 0);
		DatagramPacket sendPacket;
		InetAddress IPAddress;
		try {
			IPAddress = InetAddress.getByName(config.discoveryIpAddr);
			sendPacket = new DatagramPacket(header, header.length, IPAddress, config.discoveryPort);
			discoverySocket.send(sendPacket);
		} catch (UnknownHostException e1) {
			DEBUG("fail to find ipAddr");
			e1.printStackTrace();
		} catch (IOException e) {
			DEBUG("fail to send pcket");
			e.printStackTrace();
		}	
	}
	
	/**
	 * [Internal]
	 * To receive MultiCasting Data from discovery Socket<br />
	 * If message.id == defines.ASTCOMM_RESPONSE_DISCOVER, add to discoveryList<br />
	 * (Max. recvData length = 128 bytes. * one discovery response packet length = 96 bytes)
	 */
	private void pollDiscoveryResponse() {
		byte[] recvData = new byte[128];
		
		DatagramPacket discoveryPacket = new DatagramPacket(recvData, recvData.length);
		try {
			discoverySocket.receive(discoveryPacket);
		} catch (IOException e) {
			DEBUG("cannot receive discovery msg");
			return;
		}
		recvData = discoveryPacket.getData();
		
		DEBUG("recvData length = " + discoveryPacket.getLength());
		if(discoveryPacket.getLength() >= 12)
		{
			AstCommMessage msg = new AstCommMessage();
			msg.header = getMessageHeader(recvData);
			DEBUG("Discovery receive data length = " + msg.header.length);
			byte[] receiveData = new byte[msg.header.length];
			System.arraycopy(recvData, 12, receiveData, 0, msg.header.length);
			msg.data = receiveData;
			
			if(msg.header.magic == config.messageMagic
			&& msg.header.id == defines.ASTCOMM_RESPONSE_DISCOVER) {
				DEBUG("get discovery response from " + discoveryPacket.getSocketAddress());
				AstCommResDiscoverCommon discoveryData = new AstCommResDiscoverCommon();
				if(config.messageEndian == AstEndian.AST_LITTLEENDIAN)	{
					discoveryData.serverCommPort = AstEndian.byteLEToInt32(msg.data, 0);
				}
				else
				{
					discoveryData.serverCommPort = AstEndian.byteBEToInt32(msg.data, 0);
				}
				discoveryData.name = new String(msg.data, 4, msg.data.length-4);
				DEBUG("server Port = " + discoveryData.serverCommPort);
				InetSocketAddress serverSockAddr = new InetSocketAddress(discoveryPacket.getAddress().getHostAddress(), discoveryData.serverCommPort);

				
				if(!discoveryList.contains(serverSockAddr))
				{
					DEBUG("get new discovered server");
					if(config.listener != null) {
						INFO("send onDiscovery to Upper Level");
						config.listener.onDiscovery(serverSockAddr);
					}
					
					discoveryList.add(serverSockAddr);
					printDiscoverySocketList();
				}
			}
		}
	}

	/**
	 * Override from Thread<br/>
	 * Act as worker thread to process AstCommClient with different states
	 */
	@Override
	public void run() {
		while(stoppingThread != true) {
			//DEBUG("Thread running loop - state = "+ state);
			switch(state)
			{
				case ASTCOMMCLIENT_STATE_CONNECTED:
				{
					try {
						/** Wait for an event */
						commSelector.select(config.serverTimeout);
					} catch (IOException e) {
						e.printStackTrace();
						DEBUG("not selected anything");
						break;
					}

					/** Get list of selection keys with pending events */
					Iterator<SelectionKey> it = commSelector.selectedKeys().iterator();

					/** Process each key at a time */
					while (it.hasNext()) {
						/** Get the selection key */
						SelectionKey selKey = (SelectionKey) it.next();
						/**
						 * Remove it from the list to indicate that it is being
						 * processed
						 */
						it.remove();

						if (selKey.isValid() && selKey.isReadable()) {
							DEBUG("   Key is Readable");
							try {
								receive();
							} catch (IOException e) {
								DEBUG("No received data");
								//e.printStackTrace();
							} catch (Exception e) {
								DEBUG("fail to receive data");
								//e.printStackTrace();
							}
						}
					}
					
					
					break;
				}
				case ASTCOMMCLIENT_STATE_DISCOVERY:
				{	
					long curTime = getCurrentTime();
					if(curTime - lastDiscoveryTime > config.discoveryInterval)
					{
						// send discovery message for every discoveryInterval
						sendDiscoveryMessage();
					}
					if(discoveryDuration > 0 && (curTime - discoveryStartTime >= discoveryDuration))
					{
						INFO("Stop Discovery Request as user-defined duration reached");
						stopDiscoveryRequest();
						break;
					}
					
					// receive Discovery response
					pollDiscoveryResponse();
								
					break;
				}
				case ASTCOMMCLIENT_STATE_CONNECTING:
				{
					// do non-blocking connect
					if(nonBlockingSocketAddress == null) {
						changeState(lastState);
						break;
					}
					
					DEBUG("Try connect to " + nonBlockingSocketAddress);
					boolean connected = connectTo(nonBlockingSocketAddress, nonBlockingConnectTimeout);
					if(config.listener != null) {
						INFO("send onConnectNonBlock to Upper Level, connected ?" + connected);
						config.listener.onConnectNonBlock(connected, nonBlockingSocketAddress);
					}
					if(connected)
					{
						nonBlockingConnectTimeout = 0;
						nonBlockingSocketAddress = null;
					}
					
					break;
				}
				default:
					break;
			}
			
			// sleep thread
			try {
				Thread.sleep(defines.AST_COMM_THREAD_SLEEP);
			} catch (InterruptedException e) {
				DEBUG("fail to sleep thread");
				e.printStackTrace();
			}
		}
		
		stoppingThread = false;
		changeState(ASTCOMMCLIENT_STATE_CLOSED);
		
		INFO("Thread Stopped");
		return;
	}
	
	////// util function //////
	private static void DEBUG(String msg) {
		if(!debug_on) 
			return;
		StackTraceElement e = Thread.currentThread().getStackTrace()[2];
		String fullClassName = e.getClassName();            
        String methodName = e.getMethodName();
        int lineNumber = e.getLineNumber();
		System.out.println("["+fullClassName + "." + methodName + "():" + lineNumber+"]\t " + msg);
	}
	
	private static void INFO(String msg) {
		StackTraceElement e = Thread.currentThread().getStackTrace()[2];
		String fullClassName = e.getClassName();            
        String methodName = e.getMethodName();
        int lineNumber = e.getLineNumber();
		System.out.println("["+fullClassName + "." + methodName + "():" + lineNumber+"]\t " + msg);
	}
	
	private long getCurrentTime() {
		return System.currentTimeMillis();
	}
	
	/**
	 * Test App for AstCommClient
	 * @param args	no use
	 */
	private static void main(String[] args){
		AstCommClientConfig clientConf = new AstCommClientConfig();
		clientConf.clientTimeout = 3000;
		clientConf.serverTimeout = 3000;
		clientConf.messageEndian = AstEndian.AST_LITTLEENDIAN;
        AstCommClient client = new AstCommClient(clientConf);
       
        BufferedReader inFromUser = new BufferedReader( new InputStreamReader(System.in));
        String input;
        
        System.out.println("Test App for AstCommClient >>> ");
        do {
        	try {
	        	System.out.print("command: ");
		        input = inFromUser.readLine();
		        
		        if(input.equalsIgnoreCase("quit")) {
		        	client.disconnect();
		        	client.close();
		        	break;
		        }
		        else if(input.equalsIgnoreCase("open")) {
		        	client.open();
		        }
		        else if(input.equalsIgnoreCase("close")) {
		        	client.close();
		        }
		        else if(input.equalsIgnoreCase("connect")) {
		        	System.out.print("IP: ");
		        	String ip = inFromUser.readLine();
		        	System.out.print("Port: ");
		        	int port = Integer.parseInt(inFromUser.readLine());
		        	if(client.connectTo(ip, port) == false)
		            {
		        		INFO("connect fail!");
		            }
		        }
		        else if(input.equalsIgnoreCase("connectNonBlock")) {
		        	System.out.print("IP: ");
		        	String ip = inFromUser.readLine();
		        	System.out.print("Port: ");
		        	int port = Integer.parseInt(inFromUser.readLine());
		        	client.connectToNonBlock(ip, port);
		        }
		        else if(input.equalsIgnoreCase("stopNonBlockConnect")) {
		        	client.stopNonBlockConnect();
		        }
		        else if(input.equalsIgnoreCase("disconnect")) {
		        	client.disconnect();
		        }
		        else if(input.equalsIgnoreCase("send")) {
		        	/*byte[] data = new byte[] {(byte) 0x08, 
							(byte)0x09, 
							(byte)0x0A, 
							(byte)0x0B, 
							(byte)0x0C, 
							(byte)0x0D, 
							(byte)0x0E, 
							(byte)0x0F
						  };*/
		        	byte[] data = new byte[5000];
					client.send(0x1234, data);
		        }
		        else if(input.equalsIgnoreCase("discovery")) {
		        	client.startDiscoveryRequest(5000);
		        }
		        else if(input.equalsIgnoreCase("stop_discovery")) {
		        	client.stopDiscoveryRequest();
		        }
		        else if(input.equalsIgnoreCase("discoveryList")) {
		        	client.printDiscoverySocketList();
		        }
		        else if(input.equalsIgnoreCase("discoveryConnect")) {
		        	System.out.print("Select index: ");
		        	int index = Integer.parseInt(inFromUser.readLine());
		        	client.connectToDiscoverySock(index);
		        }
		        else {
		        	System.out.println("=========command list=========");
		        	System.out.println("quit               : quit App");
		        	System.out.println("open               : open AstCommClient");
		        	System.out.println("close              : close AstCommClient");
		        	System.out.println("connect            : connect a socket");
		        	System.out.println("disconnect         : disconnect current socket");
		        	System.out.println("connectNonBlock    : connect a socket in non-blocking mode");
		        	System.out.println("stopNonBlockConnect: stop non-blocking connect");
		        	System.out.println("discovery          : start discovery Request");
		        	System.out.println("stop_discovery     : stop discovery Request");
		        	System.out.println("discoveryList      : prtint discovery list");
		        	System.out.println("discoveryConnect   : connect selected socket in discovery list");
		        	System.out.println("send               : send 8-byte data with id=0x1234 to server");
		        	System.out.println("==============================");
		        }
        	} catch (Exception e) {
        		e.printStackTrace();
			}
        } while (true);

        DEBUG("Test App closed");
    }
}
